package com.pz.gulimall.search.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.MathUtil;
import com.pz.gulimall.common.to.es.SkuEsModelTo;
import com.pz.gulimall.search.config.EsClientConfig;
import com.pz.gulimall.search.constant.IndexConstant;
import com.pz.gulimall.search.service.ProductService;
import com.pz.gulimall.search.vo.req.SearchParamReq;
import com.pz.gulimall.search.vo.res.SearchResVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.RandomScoreFunctionBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private RestHighLevelClient esRestClient;

    /**
     *
     * @param skuEsModels 商品检索数据模型
     * @return 保存成功返回 true
     */
    @Override
    public boolean productStatusUp(List<SkuEsModelTo> skuEsModels) {
        //批量保存操作
        BulkRequest bulkRequest = new BulkRequest();
        boolean flag = true;
        skuEsModels.forEach(item -> {
            IndexRequest indexRequest = new IndexRequest(IndexConstant.PRODUCT_INDEX);
            indexRequest.id(item.getSkuId().toString());
            indexRequest.source(JSON.toJSONString(item), XContentType.JSON);
            bulkRequest.add(indexRequest);
        });

        try {
            BulkResponse response = esRestClient.bulk(bulkRequest, EsClientConfig.COMMON_OPTIONS);

            //TODO 批量保存到 ES 错误
            //统计上传失败商品的 id
            boolean hasFailures = response.hasFailures();
            List<String> collect = Arrays.stream(response.getItems()).map(BulkItemResponse::getId)
                    .collect(Collectors.toList());
            if(hasFailures){
                log.error("上架失败的商品-{}", response.buildFailureMessage());
            }else {
                log.info("上架成功的商品 skuId-{}", collect);
            }
            flag = !hasFailures;
        } catch (Exception e) {
            flag = false;
            log.error("商品批量保存到es失败--{}", e.toString());
        }
        return flag;
    }



    /**
     * 客户端前端检索业务
     *  模糊匹配检索值，按照（分类，属性，品牌，价格区间，库存）来过滤，排序，分页，高亮，聚合分析
     * @param req 请求参数
     */
    @Override
    public SearchResVo search(SearchParamReq req) {
        //1构建DSL
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //2 构建 boolQuery
        BoolQueryBuilder boolQuery = new BoolQueryBuilder();

        //首页随机获取
        if(req.getIsRandom()!=null && req.getIsRandom()==1){
            Script script = new Script("Math.random()");
            ScriptSortBuilder scriptSortBuilder = SortBuilders.scriptSort(script, ScriptSortBuilder.ScriptSortType.NUMBER).order(SortOrder.ASC);
            sourceBuilder.sort(scriptSortBuilder);
            sourceBuilder.from(0);
            sourceBuilder.size(10);
        }

        //2.1模糊匹配 must-match -> {skuTitle: keyword}
        if(!StringUtils.isEmpty(req.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", req.getKeyword()));

            //2.1.1 高亮处理 skuTitle
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }

        //2.2过滤 filter-term -> {catalogId: cataId}
        if(req.getCatId()!=null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId", req.getCatId()));
        }

        //2.3过滤 filter-terms -> {brandId: [brandIds]}
        if(req.getBrandIds()!=null && !req.getBrandIds().isEmpty()){
            boolQuery.filter(QueryBuilders.termsQuery("brandId", req.getBrandIds()));
        }

        //2.4过滤 filter-term -> {hasStock: hasStock}
        if(req.getHasStock()!=null){
            boolQuery.filter(QueryBuilders.termQuery("hasStock", req.getHasStock()==1));
        }

        //2.5过滤 filter-range -> {skuPrice: skuPriceSection}
        if(!StringUtils.isEmpty(req.getSkuPriceSection())){
            String[] section = req.getSkuPriceSection().split("_");
            //避免空字符串
            List<String> collect = Arrays.stream(section).filter((item) -> {
                return !item.isEmpty();
            }).collect(Collectors.toList());
            RangeQueryBuilder rangeQuery = new RangeQueryBuilder("skuPrice");
            if(collect.size() == 1){
                if(req.getSkuPriceSection().startsWith("_")){
                    // <= lte
                    rangeQuery.gte("0");
                    rangeQuery.lte(collect.get(0));
                }
                if(req.getSkuPriceSection().endsWith("_")){
                    // >= gte
                    rangeQuery.gte(collect.get(0));
                }
            }
            if(collect.size() == 2){
                rangeQuery.gte(collect.get(0));
                rangeQuery.lte(collect.get(1));
            }
            boolQuery.filter(rangeQuery);
        }

        //2.6过滤 filter-nested -> bool-must- term->{attrs.attrId:attrs}/terms->{attrs.attrValue:[attrs]}
        //需要注意的是 每一个 nested 字段都需要 生成一个 nested 查询
        if(req.getAttrs()!=null && !req.getAttrs().isEmpty()){
            for (String attr: req.getAttrs()){
                String[] param = attr.split("_");
                String attrId = param[0];
                String[] values = param[1].split(":"); //获取属性值多个，用 : 分开
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", Arrays.asList(values)));
                NestedQueryBuilder attrNestedQuery = QueryBuilders.nestedQuery("attrs",nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(attrNestedQuery);
            }
        }

        //3排序
        if(!StringUtils.isEmpty(req.getSort())){
            String[] sortType = req.getSort().split("_");
            SortOrder order = sortType[1].equals("ASC") ? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(sortType[0], order);
        }

        //4分页
        if(req.getPage() != null && req.getPageSize()!=null){
            sourceBuilder.size(req.getPageSize());
            sourceBuilder.from((req.getPage()-1)*req.getPageSize());
        }

        if(req.getIsRandom() == null){
            //5聚合分析
            //5.1 brandAgg--terms -> brandId
            TermsAggregationBuilder brandAgg = AggregationBuilders.terms(IndexConstant.BRAND_AGG).field("brandId").size(50);

            //5.1.1 brandAgg.brandNameAgg--terms -> brandName
            brandAgg.subAggregation(AggregationBuilders.terms(IndexConstant.BRAND_NAME_AGG).field("brandName").size(1));

            //5.1.1  brandAgg.brandImgAgg--terms -> brandImg
            brandAgg.subAggregation(AggregationBuilders.terms(IndexConstant.BRAND_IMG_AGG).field("brandImg").size(1));

            sourceBuilder.aggregation(brandAgg);

            //5.2 cataAgg--terms -> catalogId
            TermsAggregationBuilder cataAgg = AggregationBuilders.terms(IndexConstant.CATALOGY_AGG).field("catalogId").size(50);

            //5.2.1 cataAgg.catalogNameAgg--terms -> catalogName
            cataAgg.subAggregation(AggregationBuilders.terms(IndexConstant.CATALOGY_NAME_AGG).field("catalogName").size(1));

            sourceBuilder.aggregation(cataAgg);

            //5.3 attrsAgg--nested -> attrs
            NestedAggregationBuilder attrsAgg = AggregationBuilders.nested(IndexConstant.ATTRS_AGG, "attrs");

            //5.3.1 attrsAgg.attrIdAgg--terms -> attrs.attrId
            TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms(IndexConstant.ATTR_ID_AGG).field("attrs.attrId").size(50);

            //5.3.1.1 attrsAgg.attrIdAgg.attrNameAgg--terms -> attrs.attrName
            attrIdAgg.subAggregation(AggregationBuilders.terms(IndexConstant.ATTR_NAME_AGG).field("attrs.attrName").size(50));

            //5.3.1.1.2 attrsAgg.attrIdAgg.attrValueAgg--terms -> attrs.attrValue
            attrIdAgg.subAggregation(AggregationBuilders.terms(IndexConstant.ATTR_VALUE_AGG).field("attrs.attrValue").size(50));

            attrsAgg.subAggregation(attrIdAgg);
            sourceBuilder.aggregation(attrsAgg);
        }

        sourceBuilder.query(boolQuery);
        log.info("构建的DSL语句--{}", sourceBuilder.toString());

        return handlerRequest(sourceBuilder);
    }

    /**
     * 处理请求和响应数据，并粉装返回数据
     * @param sourceBuilder DSL
     * @return
     */
    public SearchResVo handlerRequest(SearchSourceBuilder sourceBuilder){
        //创建索引请求
        SearchRequest request = new SearchRequest(IndexConstant.PRODUCT_INDEX);
        SearchResVo resVo = new SearchResVo();
        request.source(sourceBuilder);
        try {
            SearchResponse esResponse = esRestClient.search(request, EsClientConfig.COMMON_OPTIONS);

            //封装数据模型
            SearchHit[] hits = esResponse.getHits().getHits();
            List<SkuEsModelTo> products = new ArrayList<>();
            for (SearchHit hit : hits){
                SkuEsModelTo skuEsModel = JSON.parseObject(hit.getSourceAsString(), SkuEsModelTo.class);
                //包装高亮
                HighlightField skuTitleHighLight = hit.getHighlightFields().get("skuTitle");
                if(skuTitleHighLight!=null){
                    skuEsModel.setSkuTitle(String.valueOf(skuTitleHighLight.getFragments()[0]));
                }
               products.add(skuEsModel);
            }
            resVo.setProducts(products);
            resVo.setTotal(esResponse.getInternalResponse().hits().getTotalHits().value);

            //获取聚合数据
            Aggregations aggregations = esResponse.getAggregations();

            //1.品牌聚合
            if(aggregations != null && aggregations.get(IndexConstant.BRAND_AGG) != null){
                List<SearchResVo.BrandVo> brands = new ArrayList<>();
                ParsedLongTerms brandAgg = aggregations.get(IndexConstant.BRAND_AGG);
                for (Terms.Bucket bucket : brandAgg.getBuckets()){
                    Long brandId = bucket.getKeyAsNumber().longValue();
                    ParsedStringTerms  brandNameAgg = bucket.getAggregations().get(IndexConstant.BRAND_NAME_AGG);
                    ParsedStringTerms brandImgAgg = bucket.getAggregations().get(IndexConstant.BRAND_IMG_AGG);
                    String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                    String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                    SearchResVo.BrandVo brandVo = new SearchResVo.BrandVo(brandId, brandName, brandImg);
                    brands.add(brandVo);
                }
                resVo.setBrands(brands);
            }

            //分类聚合
            if(aggregations != null && aggregations.get(IndexConstant.CATALOGY_AGG) != null){
                List<SearchResVo.CategoryVo> categories = new ArrayList<>();
                ParsedLongTerms cataAgg =  aggregations.get(IndexConstant.CATALOGY_AGG);
                for (Terms.Bucket bucket : cataAgg.getBuckets()){
                    Long cataId = bucket.getKeyAsNumber().longValue();
                    ParsedStringTerms cataNameAgg = bucket.getAggregations().get(IndexConstant.CATALOGY_NAME_AGG);
                    String cataName = cataNameAgg.getBuckets().get(0).getKeyAsString();
                    SearchResVo.CategoryVo categoryVo = new SearchResVo.CategoryVo(cataId, cataName);
                    categories.add(categoryVo);
                }
                resVo.setCategories(categories);
            }

            //属性聚合
            if(aggregations != null && aggregations.get(IndexConstant.ATTRS_AGG) != null){
                List<SearchResVo.AttrVo> attrs = new ArrayList<>();
                ParsedNested attrsAgg = aggregations.get(IndexConstant.ATTRS_AGG);
                ParsedLongTerms attrIds = attrsAgg.getAggregations().get(IndexConstant.ATTR_ID_AGG);
                for (Terms.Bucket bucket : attrIds.getBuckets()){
                    Long attrId = bucket.getKeyAsNumber().longValue();
                    ParsedStringTerms attrNameAgg = bucket.getAggregations().get(IndexConstant.ATTR_NAME_AGG);
                    ParsedStringTerms attrValuesAgg = bucket.getAggregations().get(IndexConstant.ATTR_VALUE_AGG);
                    String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                    StringBuilder attrValue = new StringBuilder(attrId+"_");
                    for (Terms.Bucket valueBucket : attrValuesAgg.getBuckets()){
                        attrValue.append(valueBucket.getKeyAsString());
                        attrValue.append(":");
                    }
                    attrValue.delete(attrValue.length()-1, attrValue.length());
                    SearchResVo.AttrVo attr = new SearchResVo.AttrVo(attrId, attrName, attrValue.toString());
                    attrs.add(attr);
                }
                resVo.setAttrs(attrs);
            }

        } catch (Exception e){
            log.error("搜索失败--{} -> {}", e.getClass(), e.getMessage());
        }

        return resVo;
    }

}
